﻿using SoldiHive.Infrustructure.Extensions;
using SoldiHive.Model;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SoldiHive.Core.Ratings
{
	public class RatingStrategy : IRatingStrategy
	{
		private class WeightedOpinion
		{
			public double? Rate { get; set; }
			public double? Weight { get; set; }
			
			public WeightedOpinion(double? rate, double? weight)
			{
				this.Rate = rate;
				this.Weight = weight;
			}
		}

		public Rating GetRating(double? currentPrice, AnalystOpinion analystOpinion)
		{
			var analystOpinions = new List<AnalystOpinion>() { analystOpinion };

			return this.GetRating(currentPrice, analystOpinions);
		}

		public Rating GetRating(double? currentPrice, IList<AnalystOpinion> analystOpinions)
		{
			var weightedRates = analystOpinions
				.Select(analystOpinion => new WeightedOpinion(
					this.CalculateRate(currentPrice, analystOpinion),
					this.CalculateWeight(analystOpinion)))
				.ToList();

			if (!weightedRates.Any(weightedRate => weightedRate.Rate.HasValue && weightedRate.Weight.HasValue))
			{
				return Rating.Empty;
			}

			var totalWeight = weightedRates.Select(s => s.Weight).Sum();

			Rating rating = new Rating();

			rating.Rate = weightedRates
				.Where(weightedRate => weightedRate.Rate.HasValue && weightedRate.Weight.HasValue)
				.Select(weightedRate => new
				{
					Rate = weightedRate.Rate,
					Weight = weightedRate.Weight / totalWeight
				})
				.Select(weightedRate => weightedRate.Rate * weightedRate.Weight)
				.Sum();

			if (analystOpinions.Any(p => p.PriceTarget.HasValue))
			{
				rating.PriceTarget = analystOpinions
						.Select(s => s.PriceTarget)
						.Where(p => p.HasValue)
						.Select(s => s.Value)
						.Average(); 
			}

			return rating;
		}

		private double? CalculateRate(double? currentPrice, AnalystOpinion analystOpinion)
		{
			var derivativeRate = this.GetRate(analystOpinion.Current);

			return derivativeRate;
		}

		private double? GetRate(Opinion opinion)
		{
			switch (opinion)
			{
				case Opinion.StrongBuy:
					return Rating.MaxRate * 0.95;
				case Opinion.Buy:
					return Rating.MaxRate * 0.7;
				case Opinion.Hold:
					return Rating.MaxRate * 0.5;
				case Opinion.Sell:
					return Rating.MaxRate * 0.3;
				case Opinion.StrongSell:
					return Rating.MaxRate * 0.1;
				case Opinion.NA:
				default:
					return null;
			}
		}

		private double? CalculateWeight(AnalystOpinion analystOpinion)
		{
			const double minOpinionLifeSpanDaysThreshold = 10;
			const double maxOpinionLifeSpanDaysThreshold = 90;

			if (!analystOpinion.Date.HasValue)
			{
				return 1;
			}

			var opinionLifeSpan = DateTime.Now - analystOpinion.Date.Value;
			var opinionLifeSpanDays = (double)opinionLifeSpan.Days;

			if (opinionLifeSpanDays <= minOpinionLifeSpanDaysThreshold)
			{
				return 1;
			}
			else if (opinionLifeSpanDays > maxOpinionLifeSpanDaysThreshold)
			{
				return 0.0001;
			}

			double weight = 1 - opinionLifeSpanDays / maxOpinionLifeSpanDaysThreshold;

			return weight;
		}
	}
}
